package org.infodavid.common.persistence.impl;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.infodavid.common.model.criteria.IPaginatedSearchCriteria;
import org.infodavid.common.model.criteria.ISearchCriteria;
import org.infodavid.common.model.criteria.ISortCriteria;
import org.infodavid.common.naming.NamingUtil;
import org.infodavid.common.persistence.IQueryParameter;
import org.infodavid.common.persistence.ITransaction;
import org.infodavid.common.persistence.PersistenceSession;
import org.infodavid.common.persistence.QueryParameterFactory;

/**
 * The Class PersistenceUtil.
 */
public final class PersistenceUtil {

  /** The Constant log. */
  private static final Log LOGGER = LogFactory.getLog(PersistenceUtil.class);

  /** The Constant MAX_PAGE_SIZE. */
  public static final Integer MAX_PAGE_SIZE = Integer.valueOf(1000);

  /**
   * Instantiates a new utilities.
   */
  private PersistenceUtil() {
    super();
  }

  /**
   * Release.
   * @param session the session
   */
  public static void release(final PersistenceSession session) {
    try {
      if (session != null && session.isOpen()) {
        session.close();
      }
    }
    catch (final Exception e) {
      if (e.getMessage().contains(" closed")) {
        LOGGER.debug("An error occurs while closing the session: " + e.getMessage());
      }
      else {
        LOGGER.warn("An error occurs while closing the session", e);
      }
    }
  }

  /**
   * Release.
   * @param con the JDBC connection
   */
  public static void release(final Connection con) {
    try {
      if (!(con == null || con.isClosed())) {
        con.close();
      }
    }
    catch (final Exception e) {
      if (e.getMessage().contains(" closed")) {
        LOGGER.debug("An error occurs while closing the JDBC connection: " + e.getMessage());
      }
      else {
        LOGGER.warn("An error occurs while closing the JDBC connection", e);
      }
    }
  }

  /**
   * Roll back.
   * @param transaction the JDBC transaction
   */
  public static void rollback(final ITransaction transaction) {
    try {
      if (!(transaction == null || transaction.isActive())) {
        transaction.rollback();
      }
    }
    catch (final Exception e) {
      LOGGER.warn("An error occurs while rolling back the JDBC transaction: " + e.getMessage(), e);
    }
  }

  /**
   * Release.
   * @param statement theJDBC statement
   */
  public static void release(final Statement statement) {
    try {
      if (!(statement == null || statement.isClosed())) {
        statement.close();
      }
    }
    catch (final Exception e) {
      if (e.getMessage().contains(" closed")) {
        LOGGER.debug("An error occurs while closing the JDBC statement: " + e.getMessage());
      }
      else {
        LOGGER.warn("An error occurs while closing the JDBC statement.", e);
      }
    }
  }

  /**
   * Release.
   * @param resultSet theJDBC result set
   */
  public static void release(final ResultSet resultSet) {
    try {
      if (!(resultSet == null || resultSet.isClosed())) {
        resultSet.close();
      }
    }
    catch (final Exception e) {
      if (e.getMessage().contains(" closed")) {
        LOGGER.debug("An error occurs while closing the JDBC resultSet: " + e.getMessage());
      }
      else {
        LOGGER.warn("An error occurs while closing the JDBC resultSet.", e);
      }
    }
  }

  /**
   * Gets the data source using its JNDI name.
   * @param jndiName the JNDI name
   * @return the data source
   * @throws NamingException the naming exception
   */
  public static DataSource getJndiDataSource(final String jndiName) throws NamingException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Retrieving JDBC datasource using the JNDI name: " + jndiName);
    }

    final Context context = NamingUtil.getContext();
    DataSource ds = null;

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Lookup the " + jndiName + " datasource");
    }

    final Object obj = context.lookup(jndiName);

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Object class = " + obj.getClass());
      LOGGER.debug("Object class loader = " + obj.getClass().getClassLoader());
    }

    ds = (DataSource)obj;

    return ds;
  }

  /**
   * Gets the JDBC connection using the JNDI datasource.
   * @param jndiName the JNDI name
   * @return the JDBC connection
   * @throws NamingException the naming exception
   * @throws SQLException the sQL exception
   */
  public static Connection getJndiConnection(final String jndiName) throws NamingException,
      SQLException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Retrieving JDBC connection using the JNDI name: " + jndiName);
    }

    final DataSource ds = getJndiDataSource(jndiName);
    final Connection connection = ds.getConnection();

    if (LOGGER.isDebugEnabled()) {
      if (connection != null && !connection.isClosed()) {
        LOGGER.debug("Successfully connected to "
            + connection.getMetaData().getDatabaseProductName() + " using URL "
            + connection.getMetaData().getURL());
      }
    }

    return connection;
  }

  /**
   * Apply criteria.
   * @param params the parameters
   * @param criteria the criteria
   */
  public static void applyCriteria(final List<IQueryParameter> params,
      final ISearchCriteria criteria) {
    if (criteria == null) {
      LOGGER.debug("No criteria provided, criteria skipped");
    }
    else {
      applyPagination(params, criteria);

      if (criteria instanceof ISortCriteria) {
        applySorting(params, (ISortCriteria)criteria);
      }
    }
  }

  /**
   * Apply pagination.
   * @param params the parameters
   * @param criteria the criteria
   */
  public static void applyPagination(final List<IQueryParameter> params,
      final ISearchCriteria criteria) {
    if (criteria instanceof IPaginatedSearchCriteria) {
      LOGGER.debug("Applying pagination using criteria = " + criteria + ", parameters = " + params);

      final IPaginatedSearchCriteria extendedCriteria = (IPaginatedSearchCriteria)criteria;

      final QueryParameterFactory paramFactory = QueryParameterFactory.getSingleton();
      IQueryParameter parameter = null;

      if (extendedCriteria.getPageStart() == null) {
        parameter = paramFactory.borrow();
        parameter.setType(IQueryParameter.EParameterType.FIRST_ROW).setValue(Long.valueOf(0));
        params.add(parameter);
      }
      else {
        parameter = paramFactory.borrow();
        parameter.setType(IQueryParameter.EParameterType.FIRST_ROW).setValue(
            extendedCriteria.getPageStart());
        params.add(parameter);
      }

      if (extendedCriteria.getPageSize() == null) {
        parameter = paramFactory.borrow();
        parameter.setType(IQueryParameter.EParameterType.MAX_ROWS).setValue(MAX_PAGE_SIZE);
        params.add(parameter);
      }
      else {
        parameter = paramFactory.borrow();
        parameter.setType(IQueryParameter.EParameterType.MAX_ROWS).setValue(
            extendedCriteria.getPageSize());
        params.add(parameter);
      }
    }
    else {
      LOGGER.debug("No criteria provided, pagination skipped");
    }
  }

  /**
   * Apply sorting.
   * @param params the parameters
   * @param criteria the criteria
   */
  public static void applySorting(final List<IQueryParameter> params, final ISortCriteria criteria) {
    if (criteria == null) {
      LOGGER.debug("No criteria provided, sorting skipped");
    }
    else {
      LOGGER.debug("Applying sorting using criteria = " + criteria + ", parameters = " + params);

      final QueryParameterFactory paramFactory = QueryParameterFactory.getSingleton();
      IQueryParameter parameter = null;

      for (final ISortCriteria.IColumnOrder item : criteria.getColumnsOrdering()) {
        if (item.getColumn() == null || item.getColumn().trim().length() == 0) {
          LOGGER.warn("No valid column specified in the column ordering of criteria: " + criteria);
        }
        else {
          parameter = paramFactory.borrow();

          parameter.setColumnName(item.getColumn());

          if (item.isAscending()) {
            parameter.setType(IQueryParameter.EParameterType.ORDER_ASCENDING);
          }
          else {
            parameter.setType(IQueryParameter.EParameterType.ORDER_DESCENDING);
          }

          params.add(parameter);
        }
      }
    }
  }

  /**
   * Apply sorting.
   * @param params the parameters
   * @param column the column
   */
  public static void applySorting(final List<IQueryParameter> params, final String column) {
    if (!PersistenceSession.contains(params, IQueryParameter.EParameterType.ORDER_DESCENDING,
        IQueryParameter.EParameterType.ORDER_ASCENDING)) {
      final QueryParameterFactory paramFactory = QueryParameterFactory.getSingleton();
      final IQueryParameter parameter = paramFactory.borrow();

      parameter.setColumnName(column).setType(IQueryParameter.EParameterType.ORDER_ASCENDING);
      params.add(parameter);
    }
  }

  /**
   * Borrow query parameters.
   * @param count the count
   * @return the list
   */
  public static List<IQueryParameter> borrowQueryParameters(final int count) {
    List<IQueryParameter> params = null;

    if (count > 0) {
      final QueryParameterFactory factory = QueryParameterFactory.getSingleton();
      params = new ArrayList<IQueryParameter>();
      IQueryParameter param;

      for (int i = 0; i < count; i++) {
        param = factory.borrow();
        params.add(param);
      }
    }

    return params;
  }

  /**
   * Initialize query parameters.
   * @param parameters the parameters
   * @return the list
   */
  public static List<IQueryParameter> initializeQueryParameters(
      final List<IQueryParameter> parameters) {
    final List<IQueryParameter> params;

    if (parameters == null) {
      params = new ArrayList<IQueryParameter>();
    }
    else {
      params = parameters;
    }

    return params;
  }

  /**
   * Release query parameters.
   * @param params the parameters
   */
  public static void releaseQueryParameters(final Collection<IQueryParameter> params) {
    if (!(params == null || params.isEmpty())) {
      final QueryParameterFactory factory = QueryParameterFactory.getSingleton();

      for (final IQueryParameter param : params) {
        factory.release(param);
      }
    }
  }

  /**
   * Release query parameters.
   * @param params the parameters
   */
  public static void releaseQueryParameters(final IQueryParameter... params) {
    if (!(params == null || params.length == 0)) {
      final QueryParameterFactory factory = QueryParameterFactory.getSingleton();

      for (final IQueryParameter param : params) {
        factory.release(param);
      }
    }
  }

  /**
   * Validate.
   * @param session the session
   */
  public static void validate(final PersistenceSession session) {
    if (session == null) {
      throw new IllegalArgumentException("session");
    }
  }
}
